using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Reflection;
using Microsoft.Xna.Framework.Input;
using Xfinity.Engine;
using System.Text.RegularExpressions;
using Xfinity.Settings;

namespace Xfinity.Console
{

    #region Extra stuff.
    /// <summary>
    /// A Command.
    /// </summary>
    public class Command
    {
        /// <summary>
        /// The name.
        /// </summary>
        public string Name;
        /// <summary>
        /// The description.
        /// </summary>
        public string Description;
        /// <summary>
        /// The help.
        /// </summary>
        public string Help;
        /// <summary>
        /// The delegate.
        /// </summary>
        public ExecuteCommandFromString Execute;
    }





    /// <summary>
    /// A callback that processes and executes it.
    /// </summary>
    /// <param name="Command">The Command and its parameters.</param>
    public delegate void ExecuteCommandFromString(string[] command);

    /// <summary>
    /// Thrown when a Command can't be executed.
    /// </summary>
    [Serializable]
    public class CommandFailedException : Exception
    {
        private string[] commands;

        /// <summary>
        /// Gets the commands.
        /// </summary>
        /// <value>The commands.</value>
        public string[] Commands
        {
            get { return commands; }
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="CommandFailedException"/> class.
        /// </summary>
        /// <param name="commands">The commands.</param>
        public CommandFailedException(string[] commands)
            : base()
        {
            this.commands = commands;
        }
        /// <summary>
        /// Constructs the exception.
        /// </summary>
        /// <param name="commands">The Command line.</param>
        /// <param name="e">The original exception.</param>
        public CommandFailedException(string[] commands, Exception e)
            : base("", e)
        {
            this.commands = commands;
        }

    }
    /// <summary>
    /// Thrown when a Command can't be executed.
    /// </summary>
    [Serializable]
    public class InvalidCommandException : Exception
    {
        private string[] commands;

        /// <summary>
        /// Gets the commands.
        /// </summary>
        /// <value>The commands.</value>
        public string[] Commands
        {
            get { return commands; }
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="CommandFailedException"/> class.
        /// </summary>
        /// <param name="commands">The commands.</param>
        public InvalidCommandException(string[] commands)
            : base()
        {
            this.commands = commands;
        }
        /// <summary>
        /// Constructs the exception.
        /// </summary>
        /// <param name="commands">The Command line.</param>
        /// <param name="e">The original exception.</param>
        public InvalidCommandException(string[] commands, Exception e)
            : base("", e)
        {
            this.commands = commands;
        }

    }

    #endregion

    /// <summary>
    /// Parses the commands to execute from strings.
    /// </summary>
    [DebuggerStepThrough]
    public static class CommandParser
    {
        /// <summary>
        /// Returns the matching commands from a regular expression.
        /// </summary>
        /// <param name="search">The raw regular expression text.</param>
        /// <param name="includeDescriptions">Whether to include descriptions while searching.</param>
        /// <param name="matchingCommands">The matching commands.</param>
        /// <returns>The matching strings.</returns>
        public static List<string> GetMatchingCommands(string search, bool includeDescriptions, out List<Command> matchingCommands)
        {
            List<string> matches = new List<string>();
            matchingCommands = new List<Command>();
            Regex regex = new Regex(search, RegexOptions.IgnoreCase | RegexOptions.Singleline);
            foreach (Command command in CommandParser.Commands.Values)
            {
                string line;
                if (includeDescriptions)
                {
                    line = DescribeCommand(command);
                }
                else
                {
                    line = command.Name;
                }
                Match match = regex.Match(line);
                if (match.Success)
                {
                    matches.Add(line);
                    matchingCommands.Add(command);
                }
            }
            matches.Sort();
            return matches;
        }
        /// <summary>
        /// Describes a Command.
        /// </summary>
        /// <param name="command">The command.</param>
        /// <returns>A string description of the Command.</returns>
        public static string DescribeCommand(Command command)
        {
            string line = command.Name;
            if (!String.IsNullOrEmpty(command.Description))
            {
                line = line + ": " + command.Description;
            }
            return line;
        }
        /// <summary>
        /// Registers a Command.
        /// </summary>
        /// <param name="Command">The Command.</param>
        public static void RegisterCommand(Command command)
        {
            if (commands.ContainsKey(command.Name.ToLower()))
            {
                System.Diagnostics.Debug.Fail("Command already bound.");
                return;
            }
            commands.Add(command.Name.ToLower(), command);
        }

        /// <summary>
        /// Processes a Command.
        /// </summary>
        /// <param name="commandLine">The Command line.</param>
        public static void ProcessCommand(string commandLine)
        {
            string[] splitCommands = commandLine.Split(' ');
            try
            {
                ExecuteCommandFromString callback = commands[splitCommands[0].ToLower()].Execute;
                callback(splitCommands);
            }
            catch (KeyNotFoundException)
            {
                throw new InvalidCommandException(splitCommands);
            }
            //catch (Exception e)
            //{
            //    throw new CommandFailedException(splitCommands, e);
            //}
        }
        /// <summary>
        /// Reconstructs the arguments.
        /// </summary>
        /// <param name="commands">The seperated commands.</param>
        /// <returns>The original Command line.</returns>
        public static string ReconstructArguments(string[] commands)
        {
            string arguments = string.Join(" ", commands, 1, commands.Length - 1);
            return arguments;
        }

        private static Dictionary<string, Command> commands =
            new Dictionary<string, Command>();

        /// <summary>
        /// The list of commands and their processors.
        /// </summary>
        /// <value>The commands.</value>
        public static Dictionary<string, Command> Commands
        {
            get { return commands; }
        }

        /// <summary>
        /// Scans all loaded assemblies for bound commands.
        /// </summary>
        public static void ScanForBoundCommands()
        {
            Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies();
            ScanForBoundCommands(assemblies);
        }

        /// <summary>
        /// Scans for bound commands in a list of assemblies.
        /// </summary>
        /// <param name="assemblies">The assemblies.</param>
        public static void ScanForBoundCommands(IEnumerable<Assembly> assemblies)
        {
            foreach (Assembly a in assemblies)
            {
                Type[] types = a.GetTypes();
                foreach (Type t in types)
                {
                    foreach (MethodInfo method in t.GetMethods(BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Static))
                    {
                        BindCommandAttribute[] bca =
                            (BindCommandAttribute[])method.GetCustomAttributes(typeof(BindCommandAttribute), false);
                        foreach (BindCommandAttribute attr in bca)
                        {
                            Command command = new Command();
                            System.Diagnostics.Debug.WriteLine(attr.CommandName + " is bound to " + method.Name, "Bound Command found");
                            command.Name = attr.CommandName;
                            command.Help = attr.Help;
                            command.Description = attr.Description;
                            command.Execute = (ExecuteCommandFromString)
                                ExecuteCommandFromString.CreateDelegate(typeof(ExecuteCommandFromString),
                                    null,
                                    method);

                            RegisterCommand(command);

                            if (!string.IsNullOrEmpty(attr.Setting) && EngineSettings.GetSetting<object>(attr.Setting).GetType() == typeof(Keys))
                            {
                                InputHandler.BindToCommand(EngineSettings.GetSetting<Keys>(attr.Setting), attr.CommandName);
                            }
                            else if (attr.DefaultBoundKey != Keys.None)
                            {
                                InputHandler.BindToCommand(attr.DefaultBoundKey, attr.CommandName);
                            }
                        }
                    }
                }
            }

            // because a whole bunch of reference types were just created and are no longer being used, they should be
            // manually collected, otherwise it will give the garbage collecter the wrong idea.
            GC.Collect();
        }
    }
}